package com.broada.apm.es.indices.support;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateUtils;

import com.broada.apm.indicator.api.common.ApmDataIndicatorException;
import com.broada.apm.indicator.api.util.DateUtil;
import com.broada.apm.model.ErrorRecord;
import com.broada.apm.model.Scope;
import com.broada.apm.model.TransactionDetail;
import com.broada.apm.model.TransactionRecord;
import com.broada.apm.model.performance.BasePerformance;
import com.broada.apm.utils.ConfigureProperties;

/**
 * 
 * perf index管理类
 *
 * 2015-4-15 jialin getPerfIndices()总是从某月1号开始查 2015-4-15 louyi perf索引增加alias
 * 
 * @author louyi Create By 2014-11-21 上午10:56:20
 */
public class PerfIndicesBuilder extends AbstractIndicesBuilder {
	
	private static PerfIndicesBuilder perfIndicesBuilder = new PerfIndicesBuilder();
	private final String RES_INDEX_RULE = "_r_y";
	private String perf_data_build_rule = ConfigureProperties.getString("perf.data.build.rule", "d").toLowerCase();
	
	public final String TYPE = Scope.APMRUNNING_PERFS.getType();
	
	private final String DAY_RULE = "_d";
	
	/**
	 * 所有应用系统自身性能数据
	 */
	public final String APM_PERF_Y_M = "apm_perf_y%s_m%s";
	/**
	 * 服务节点维度性能数据以及自身性能数据
	 */
	public final String APM_PERF_A_Y_M = "apm_perf_a%s_y%s_m%s";
	/**
	 * 应用系统的维度性能数据 每个应用系统、每个维度一个索引
	 */
	public final String APM_PERF_A_D_Y_M = "apm_perf_a%s_d%s_y%s_m%s";
	/**
	 * 关键交易性能数据（所有维度） 每个应用系统一个索引
	 */
	public final String APM_PERF_A_K_Y_M = "apm_perf_a%s_k_y%s_m%s";
	/**
	 * 所以交易性能数据（所有维度） 每个应用系统一个索引
	 */
	public final String APM_PERF_A_R_Y_M = "apm_perf_a%s_r_y%s_m%s";
	/**
	 * 虚拟站点性能数据（所有维度）每个虚拟站点一个索引
	 */
	public final String APM_PERF_A_VY_Y_M = "apm_perf_a%s_vy%s_m%s";
	/**
	 * 访问方式性能数据（所有维度）每个虚拟站点一个索引
	 */
	public final String APM_PERF_A_SY_Y_M = "apm_perf_a%s_sy%s_m%s";
	
	public final String PINPOINT_SPAN_Y_M_D = "pinpoint_span_y%s_m%s_d%s";

	public static PerfIndicesBuilder getInstance() {
		return perfIndicesBuilder;
	}

	private PerfIndicesBuilder() {
	}

	/**
	 * 根据开始&截止时间 计算需要使用的索引
	 * 
	 * @throws Exception
	 */
	public String[] getPerfIndices(String format, String[] param, Date start, Date end) throws ApmDataIndicatorException {
		String[] indices = null;
		
		// 查询的起始时间
		String indexStart = getIndexTimeStartOrEnd(format, param, start);
		// 过滤索引，存在则Add
		indices = existsIndex(indexStart) ? (String[]) ArrayUtils.add(indices, indexStart) : indices;
		
		// 查询的结束时间
		String indexEnd = getIndexTimeStartOrEnd(format, param, end);
		// 判断查询起始时间的索引与查询结束时间的索引是否相同，不同则add，并补充中间缺少的
		if (!indexStart.equals(indexEnd)) {
			indices = existsIndex(indexEnd) ? (String[]) ArrayUtils.add(indices, indexEnd) : indices;
			
			Calendar calendar = createCalendar(start);
			setCalendar(calendar, format);
			while (calendar.getTimeInMillis() < end.getTime()) {
				
				Object[] args = ArrayUtils.add(param, String.valueOf(calendar.get(Calendar.YEAR)));
				args = ArrayUtils.add(args, String.valueOf(calendar.get(Calendar.MONTH) + 1));
				String index = String.format(format, args);
				// 目前只支持资源按天分索引
				if ("d".equals(perf_data_build_rule) && APM_PERF_A_R_Y_M.equals(format)) {
					index += DAY_RULE + calendar.get(Calendar.DAY_OF_MONTH);
					calendar.add(Calendar.DAY_OF_MONTH, 1);
				} else {
					calendar.add(Calendar.MONTH, 1);
				}
				// 过滤索引，存在则Add
				if (!index.equals(indexEnd)) {
					indices = existsIndex(index) ? (String[]) ArrayUtils.add(indices, index) : indices;
				}
			}
		}
		
		return indices;
	}
	
	private String getIndexTimeStartOrEnd(String format, String[] param, Date timeData) throws ApmDataIndicatorException {
		Calendar calendar = createCalendar(timeData);
		
		Object[] args = ArrayUtils.add(param, String.valueOf(calendar.get(Calendar.YEAR)));
		args = ArrayUtils.add(args, String.valueOf(calendar.get(Calendar.MONTH) + 1));
		String index = String.format(format, args);
		// 按天索引
		if ("d".equals(perf_data_build_rule) && APM_PERF_A_R_Y_M.equals(format)) {
			index += DAY_RULE + calendar.get(Calendar.DAY_OF_MONTH);
		}
		return index;
	}
	
	// 创建日历
	private Calendar createCalendar(Date date) {
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		calendar.set(Calendar.HOUR_OF_DAY, 0);
		calendar.set(Calendar.MINUTE, 0);
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MILLISECOND, 0);
		return calendar;
	}
	
	// 创建日历
	private Calendar createCalendar(Long date) {
		Calendar calendar = Calendar.getInstance();
		calendar.setTimeInMillis(date);
		calendar.set(Calendar.HOUR_OF_DAY, 0);
		calendar.set(Calendar.MINUTE, 0);
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MILLISECOND, 0);
		return calendar;
	}
	
	// 根据需求设置日历
	private void setCalendar(Calendar calendar, String format) {
		// 目前只支持资源按天分索引
		if ("d".equals(perf_data_build_rule) && APM_PERF_A_R_Y_M.equals(format)) {
			calendar.add(Calendar.DAY_OF_MONTH, 1);
		} else {
			calendar.add(Calendar.MONTH, 1);
		}
	}

	/**
	 * 根据月份查询需要清除的index。过滤"apm_perf_a*"
	 * 
	 * @param month
	 * @throws Exception
	 */
	public String[] queryClearPerfIndex(int months, String format) throws ApmDataIndicatorException {
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(new Date());
		calendar.add(Calendar.MONTH, 0 - months);
		// 获取需要保留的开始年份和月份
		int destYear = calendar.get(Calendar.YEAR);
		int destMonth = calendar.get(Calendar.MONTH) + 1;

		String[] indexs = null;

		for (String index : searchIndex(format)) {
			int y = index.indexOf("_y");
			int m = index.indexOf("_m");
			int srcYear = 0;
			int srcMonth = 0;
			if(y > -1)
				srcYear = Integer.valueOf(index.substring(y + 2, m));
			else {
				y = index.indexOf("_sy") > -1 ? index.indexOf("_sy") : index.indexOf("_vy");
				if (y > -1) continue;
				srcYear = Integer.valueOf(index.substring(y + 3, m));
			}
			// 资源按天生成索引
			if (index.indexOf(RES_INDEX_RULE) > -1 && index.lastIndexOf(DAY_RULE) > index.indexOf(RES_INDEX_RULE)) {
				int d = index.lastIndexOf(DAY_RULE);
				srcMonth = Integer.valueOf(index.substring(m + 2, d));
			} else {
				srcMonth = Integer.valueOf(index.substring(m + 2));
			}
			if (srcYear < destYear || (srcYear == destYear && srcMonth < destMonth))
				indexs = (String[]) ArrayUtils.add(indexs, index);
		}
		return indexs;
	}

	/**
	 * 
	 * 根据月份清除index。过滤"apm_perf*"
	 * 
	 * @param month
	 * @throws Exception
	 */
	public boolean clearPerfIndex(int months) throws ApmDataIndicatorException {
		return deleteIndex(queryClearPerfIndex(months, "apm_perf*"));
	}

	/**
	 * 根据appId来清除索引数据
	 * 
	 * @param months
	 * @param appIds
	 * @return
	 * @throws ApmDataIndicatorException
	 */
	public boolean clearPerfIndexByAppId(int months, String... appIds) throws ApmDataIndicatorException {
		String[] indexs = null;
		for (String appId : appIds) {
			String appIndex[] = queryClearPerfIndex(months, "apm_perf_a" + appId + "*");
			indexs = (String[]) ArrayUtils.addAll(indexs, appIndex);
		}

		return deleteIndex(indexs);
	}

	
	/**
	 * 根据性能实体类解析 获得需要使用的索引
	 * 
	 * @param performance
	 * @return
	 * @throws Exception
	 */
	public String[] getPerfIndices(BasePerformance performance) throws Exception {
		String[] indices = null;
		DateUtil dateUtil = new DateUtil(performance.getTimestamp());
		String year = String.valueOf(dateUtil.getYear());
		String month = String.valueOf(dateUtil.getMonth());
		String day = String.valueOf(dateUtil.getDay());
		String appId = performance.getApplicationId();
		String serviceId = performance.getServiceId();
		String domainId = performance.getDomainId();
		String dimId = performance.getDimensionId();
		String dimType = performance.getDimensionType();
		String resId = performance.getResourceId();
		String subProtocol = performance.getSubProtocol();
		boolean isKey = performance.isKey();

		if (StringUtils.isEmpty(appId))
			return ArrayUtils.EMPTY_STRING_ARRAY;

		if (resId != null) {
			if (!"d".equals(perf_data_build_rule))
				indices = (String[]) ArrayUtils.add(indices, String.format(APM_PERF_A_R_Y_M, appId, year, month));
			else
				indices = (String[]) ArrayUtils.add(indices, String.format(APM_PERF_A_R_Y_M, appId, year, month) + DAY_RULE
						+ day);
			if (isKey)
				indices = (String[]) ArrayUtils.add(indices, String.format(APM_PERF_A_K_Y_M, appId, year, month));// 关键交易
		} else {
			if (serviceId == null) {
				if (dimId != null) {
					indices = (String[]) ArrayUtils.add(indices, String.format(APM_PERF_A_D_Y_M, appId, dimType, year, month));// 应用系统维度性能数据
				} else {
					indices = (String[]) ArrayUtils.add(indices, String.format(APM_PERF_Y_M, year, month)); // 应用系统自身性能数据
				}
			} else if (domainId != null) {
				indices = (String[]) ArrayUtils.add(indices, String.format(APM_PERF_A_VY_Y_M, appId,  year, month));
			} else if (subProtocol != null) {
				indices = (String[]) ArrayUtils.add(indices, String.format(APM_PERF_A_SY_Y_M, appId,  year, month));
			}  else {
				indices = (String[]) ArrayUtils.add(indices, String.format(APM_PERF_A_Y_M, appId, year, month)); // 服务节点维度性能数据以及自身性能数据
			}
		}
		// 验证索是否存在 不存在则新建
		for (String index : indices) {
			buildIndex(index);
		}
		return indices == null ? ArrayUtils.EMPTY_STRING_ARRAY : indices;
	}
	
	public String[] getErrorIndices(ErrorRecord error) throws Exception {
	    DateUtil du = new DateUtil(error.getTimestamp());
	    return new String[] {
	    		String.format("apm_error_a%s_y%s_m%s_d%s",error.getApplicationId(),du.getYear(),du.getMonth(),du.getDay())
	    };
	}
	
	public String[] getErrorIndices(String applicationId, Date start, Date end) throws ApmDataIndicatorException {
	    List<String> idxs = new ArrayList<String>();
		for (Date s = start; s.before(end) || DateUtils.isSameDay(s, end);) {
			DateUtil du = new DateUtil(s);
			String index = String.format("apm_error_a%s_y%s_m%s_d%s", applicationId, du.getYear(), du.getMonth(), du
					.getDay());
			// 过滤索引，存在则Add
			if (existsIndex(index))
				idxs.add(index);
			s = DateUtils.addDays(s, 1);
		}
	    String[] indices = idxs.toArray(new String[idxs.size()]);
	    
        return indices;
	}

	public String[] getTransactionIndices(TransactionRecord tr) throws Exception {
	    DateUtil du = new DateUtil(tr.getStartTime()!=null?tr.getStartTime():(tr.getEndTime()!=null?tr.getEndTime():new Date()));
	    return new String[] {
	         String.format("apm_tran_y%s_m%s_d%s",du.getYear(),du.getMonth(),du.getDay())   
	    };
	}
	
	public String[] getTransactionIndices(Date start, Date end) throws ApmDataIndicatorException {
		List<String> idxs = new ArrayList<String>();
		for (Date s = start; s.before(end) || DateUtils.isSameDay(s, end);) {
			DateUtil du = new DateUtil(s);
			String index = String.format("apm_tran_y%s_m%s_d%s", du.getYear(), du.getMonth(), du.getDay());
			// 过滤索引，存在则Add
			if (existsIndex(index))
				idxs.add(index);
			s = DateUtils.addDays(s, 1);
		}
		String[] indices = idxs.toArray(new String[idxs.size()]);
		
		return indices;
	}
	
	public String[] getTransactionDetailIndices(TransactionDetail td) throws Exception {
		DateUtil du = new DateUtil(td.getStartTime() != null ? td.getStartTime()
					: (td.getEndTime() != null ? td.getEndTime() : new Date()));
		return new String[] {
	         String.format("apm_trade_detail_y%s_m%s_d%s", du.getYear(), du.getMonth(), du.getDay())   
	    };
	}
	
	public String[] getTransactionDetailIndices(Date start, Date end) throws ApmDataIndicatorException {
		List<String> idxs = new ArrayList<String>();
		for (Date s = start; s.before(end) || DateUtils.isSameDay(s, end);) {
			DateUtil du = new DateUtil(s);
			String index = String.format("apm_trade_detail_y%s_m%s_d%s", du.getYear(), du.getMonth(), du.getDay());
			// 过滤索引，存在则Add
			if (existsIndex(index))
				idxs.add(index);
			s = DateUtils.addDays(s, 1);
		}
		String[] indices = idxs.toArray(new String[idxs.size()]);
		
		return indices;
	}
	
	public String[] getErrorDetailIndices(com.broada.apm.model.ErrorDetail error) {
		DateUtil du = new DateUtil(error.getTimestamp());
		return new String[] { String.format("apm_detail_error_a%s_y%s_m%s_d%s", error.getApplicationId(), du.getYear(),
				du.getMonth(), du.getDay()) };
	}
	
	public String[] getErrorDetailIndices(String appId, Date start, Date end) throws ApmDataIndicatorException {
	    List<String> idxs = new ArrayList<String>();
		for (Date s = start; s.before(end) || DateUtils.isSameDay(s, end);) {
			DateUtil du = new DateUtil(s);
			String index = String.format("apm_detail_error_a%s_y%s_m%s_d%s", appId, du.getYear(), du.getMonth(), du.getDay());
			// 过滤索引，存在则Add
			if (existsIndex(index))
				idxs.add(index);
			s = DateUtils.addDays(s, 1);
		}
	    String[] indices = idxs.toArray(new String[idxs.size()]);
	    
        return indices;
	}

	
	/**
	 * 新方法从这里开始
	 */
	public String[] getPerfIndices(String format, Long start, Long end) throws ApmDataIndicatorException {
		String[] indices = null;
		
		Calendar calendarStart = createCalendar(start);
		Calendar calendarEnd = createCalendar(end);
		// 查询的起始时间
		String indexStart = getIndexTimeStartOrEnd(format, calendarStart);
		// 过滤索引，存在则Add
		indices = existsIndex(indexStart) ? (String[]) ArrayUtils.add(indices, indexStart) : indices;
		
		// 查询的结束时间
		String indexEnd = getIndexTimeStartOrEnd(format, calendarEnd);
		// 判断查询起始时间的索引与查询结束时间的索引是否相同，不同则add，并补充中间缺少的
		if (!indexStart.equals(indexEnd)) {
			indices = existsIndex(indexEnd) ? (String[]) ArrayUtils.add(indices, indexEnd) : indices;
			
			calendarStart.add(Calendar.DAY_OF_MONTH, 1);
			while (calendarStart.getTimeInMillis() < end) {
				Object[] args = { String.valueOf(calendarStart.get(Calendar.YEAR)), String.valueOf(calendarStart.get(Calendar.MONTH) + 1),
						String.valueOf(calendarStart.get(Calendar.DAY_OF_MONTH)) };
				String index = String.format(format, args);
				calendarStart.add(Calendar.DAY_OF_MONTH, 1);
				// 过滤索引，存在则Add
				if (!index.equals(indexEnd)) {
					indices = existsIndex(index) ? (String[]) ArrayUtils.add(indices, index) : indices;
				}
			}
		}
		return indices;
	}
	
	private String getIndexTimeStartOrEnd(String format, Calendar calendar) throws ApmDataIndicatorException {
		Object[] args = { String.valueOf(calendar.get(Calendar.YEAR)), String.valueOf(calendar.get(Calendar.MONTH) + 1),
				String.valueOf(calendar.get(Calendar.DAY_OF_MONTH)) };
		String index = String.format(format, args);
		return index;
	}
}